<?php
/**
 * Holds class Cache_Container_File
 *
 * @author daantimmer
 *
 * @package Library
 * @subpackage Cache
 */

/**
 * File-interface for the cache system
 *
 * @author daantimmer
 */
class Cache_Container_File extends Cache_Container{
	
	/**
	 * @var string
	 */
	private $location;
	
	/**
	 * @var file-pointer
	 */
	private $manager;
	
	/**
	 * @var array
	 */
	private $managerData;
	
	/**
	 * @var integer
	 */
	const GC_COUNT_MAX = 10;
	
	/**
	 * Constructor
	 *
	 * Will attempt to open a file named manager.cache This file is used
	 * for storing cache data. The cached data itself is not written in this file
	 * but in seperate files in $location
	 *
	 * @param string $location location where to cache
	 * @return void
	 */
	protected function __construct(){
		parent::__construct();
		
		$location = APP . "/cache";
		
		$this->location = rtrim(trim($location),DIRECTORY_SEPARATOR);
		try{$this->manager = fopen($location.DIRECTORY_SEPARATOR."manager.cache","a+");
		}catch(Exception $e){var_dump($e);}
		
		if($this->isAvailable()){
			$this->managerData = unserialize(
//									gzuncompress(
										fgets(
											$this->manager
										)
//									)	//gzuncompress
								);	//unserialize
		}
		
		if(isset($this->managerData["gc"]) && $this->managerData["gc"] !== 0)
				$this->managerData["gc"]--;
		else	$this->managerData["gc"] = self::GC_COUNT_MAX;
	}
	
	public static function instance($class = __CLASS__){
		return parent::instance($class);
	}
	
	/**
	 * Destructor
	 *
	 * Each 10th init of Cache_Container_File the garbage collector will be called
	 * to clean up old data in the cache. This might be increased to a higher value
	 * depending on performance impact
	 *
	 * @return void
	 */
	public function __destruct(){
		if($this->isAvailable()){
			
			if($this->managerData["gc"] === 0)	$this->gc();
			
			ftruncate($this->manager,0);
			fwrite($this->manager,
//					gzcompress(
						serialize(
							$this->managerData
						)	//serialize
//					)	//gzcompress
				);
			fclose($this->manager);
		}
	}
	
	/**
	 * Check if the manager is available
	 *
	 * Returns true if a filepointer is set
	 *
	 * @see Cache_Container::isAvailable()
	 * @return boolean
	 */
	public function isAvailable(){
		if($this->manager)	return true;
		return false;
	}
	
	/**
	 * Writes $data to file $key->getKey().cache
	 *
	 * Will use gzcompression to compress the data
	 *
	 * @see Cache_Container::write()
	 * @return void
	 */
	public function write(Cache_Key $key,&$data,$timeout){
		$this->managerData[$key->getKey()] = array("timeout" => $timeout+time(), "key" => serialize($key));
		
		$fp = fopen($this->location . DIRECTORY_SEPARATOR . $key->getKey() . ".cache","w+");
		fwrite($fp,
				gzcompress(	//compress
					$data
				)			//compress
			);
		fclose($fp);
	}
	
	/**
	 * Read contents of $key
	 *
	 * @see Cache_Container::read()
	 * @return mixed or returns null on failure
	 */
	public function read(Cache_Key $key){
		if($this->check($key)){
			if($this->managerData[$key->getKey()]["timeout"] >= time()){
				$fp = @fopen($this->location . DIRECTORY_SEPARATOR . $key->getKey() . ".cache","r");
				if(!$fp) return null;
				$data = "";
				while(($_data = fgets($fp)) !== false)		$data.=$_data;
				fclose($fp);
				if($data && $data != "")
					return (
							gzuncompress(	//compress
								$data
							)				//compress
						);
				return null;
			}
			return null;
		}
		return null;
	}
	
	/**
	 * Check if $key is set
	 *
	 * @param Cache_Key $key
	 * @return boolean
	 */
	public function __isset($key){
		return $this->check($key);
	}
	
	/**
	 * Check if $key is set
	 *
	 * @param Cache_Key $key
	 * @return boolean
	 */
	public function check($key){
		return isset($this->managerData[$key->getKey()]);
	}
	
	/**
	 * Removes $key from file system and manager
	 *
	 * @see Cache_Container::remove()
	 * @return boolean
	 */
	public function remove(Cache_Key $key){
		unset($this->managerData[$key->getKey()]);
		return @unlink($this->location . DIRECTORY_SEPARATOR . $key->getKey() . ".cache");
	}
	
	/**
	 * Garbage collector, removes all data that has its cache-timer expired
	 *
	 * @see Cache_Container::gc()
	 * @return void
	 */
	public function gc(){
		foreach ($this->managerData as $key => $data) {
			if($key != "gc" && $data["timeout"] < time()){
				$this->remove(unserialize($data["key"]));
			}
		}
	}
	
	public function getStatistics(){
		$statistics = array();
		foreach ($this->managerData as $key => $data) {
			if($data && ($key = unserialize($data["key"]))){
				$file = $this->location . DIRECTORY_SEPARATOR . $key->getKey(). ".cache";
				$statistics[] = new Cache_Stats($file,$key->getKey(), $data["timeout"],$data["timeout"] < time());
			}
		}
		return $statistics;
	}
	
	public function clear(){
		foreach ($this->managerData as $key => $data) {
			if($key = unserialize($data["key"]))
				$this->remove($key);
		}
	}
}